package com.michaelbulava.TFS.ui.controls.workspaces;

import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
import com.intellij.openapi.ui.ComponentWithBrowseButton;
import com.intellij.openapi.ui.TextComponentAccessor;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.ui.SortedComboBoxModel;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.JBPanel;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.ui.table.JBTable;
import com.intellij.uiDesigner.core.GridConstraints;
import com.intellij.uiDesigner.core.GridLayoutManager;
import com.intellij.util.EventDispatcher;
import com.intellij.util.ui.ComboBoxCellEditor;
import com.michaelbulava.TFS.Messages;
import com.michaelbulava.TFS.ui.dialogs.vc.ServerItemTreeDialog;
import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.WorkingFolderType;
import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.Workspace;
import javafx.scene.control.ComboBox;
import org.jdesktop.swingx.renderer.DefaultTableRenderer;

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.tree.TreeCellEditor;
import java.awt.*;
import java.awt.List;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.*;

/**
 * Created by MichaelA on 9/24/2015.
 */
public class WorkingFolderDataTable2 extends JBPanel{
    private JBTable WorkingFolderTable;

    private static final String TYPE_ACTIVE = Messages.getString("WorkingFolderDataTable.TypeActive");
    private static final String TYPE_CLOAKED = Messages.getString("WorkingFolderDataTable.TypeCloaked");

    private final EventDispatcher<ChangeListener> myEventDispatcher = EventDispatcher.create(ChangeListener.class);

    private Workspace workspace;
    private WorkingFolderDataModel model;

    public WorkingFolderDataTable2(){
        super();
        layoutUI();
    }

    private void layoutUI(){
        final GridLayoutManager layout = new GridLayoutManager(2,1);
        layout.setMargin(new Insets(4, 4, 4, 4));
        setLayout(layout);

        final JBLabel label1 = new JBLabel();
        label1.setText("Working Folders:");
        add(label1, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST,
                GridConstraints.FILL_NONE,
                GridConstraints.SIZEPOLICY_FIXED,
                GridConstraints.SIZEPOLICY_FIXED,
                null, null, null, 0, false));

        final JPanel panel1 = new JPanel();
        panel1.setLayout(new GridLayoutManager(1, 1, new Insets(0, 0, 0, 0), -1, -1));
        add(panel1, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER,
                GridConstraints.FILL_BOTH,
                GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
                GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
                null, null, null, 0, false));

        final JBScrollPane scrollPane1 = new JBScrollPane();
        add(scrollPane1, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER,
                GridConstraints.FILL_BOTH,
                GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW,
                GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW,
                null, null, null, 0, false));



        WorkingFolderTable = new JBTable();
        model = new WorkingFolderDataModel();
        WorkingFolderTable.setModel(model);
        WorkingFolderTable.setDefaultRenderer(WorkingFolderType.class, new WorkingFolderTypeCellRenderer());
        WorkingFolderTable.setDefaultRenderer(String.class, new WorkingFolderPathCellRenderer());
        WorkingFolderTable.setDefaultEditor(WorkingFolderType.class, new WorkingFolderTypeCellEditor());
        WorkingFolderTable.setDefaultEditor(String.class,new PathTableCellEditor());
        scrollPane1.setViewportView(WorkingFolderTable);
    }

    public void setWorkingFolderDataCollection(WorkingFolderDataCollection data){
        model.setData(data);
    }

    public void addChangeListener(ChangeListener value){
        myEventDispatcher.addListener(value);
    }

    public void setWorkspace(Workspace workspace){
        this.workspace = workspace;
        if (this.workspace == null)
            model.setData(new WorkingFolderDataCollection());
        else
            model.setData(new WorkingFolderDataCollection(this.workspace));
    }

    private class WorkingFolderDataModel extends AbstractTableModel {
        private WorkingFolderData[] data = new WorkingFolderData[0];

        private java.util.List<TableModelListener> listeners = new ArrayList<TableModelListener>();

        public static final String COLUMN_TYPE = "type";
        public static final String COLUMN_SERVER = "server";
        public static final String COLUMN_LOCAL = "local";


        public WorkingFolderDataModel() {

        }

        public void setData(WorkingFolderDataCollection data){
            this.data = data.getWorkingFolderData();

            ApplicationManager.getApplication().invokeAndWait(new Runnable() {

                @Override
                public void run() {
                    fireTableDataChanged();
                }
            }, ModalityState.stateForComponent(WorkingFolderDataTable2.this));
        }

        /**
         * Returns the number of rows in the model. A
         * <code>JTable</code> uses this method to determine how many rows it
         * should display.  This method should be quick, as it
         * is called frequently during rendering.
         *
         * @return the number of rows in the model
         * @see #getColumnCount
         */
        @Override
        public int getRowCount() {
            return data.length;
        }

        /**
         * Returns the number of columns in the model. A
         * <code>JTable</code> uses this method to determine how many columns it
         * should create and display by default.
         *
         * @return the number of columns in the model
         * @see #getRowCount
         */
        @Override
        public int getColumnCount() {
            return 3;
        }

        /**
         * Returns the name of the column at <code>columnIndex</code>.  This is used
         * to initialize the table's column header name.  Note: this name does
         * not need to be unique; two columns in a table can have the same name.
         *
         * @param columnIndex the index of the column
         * @return the name of the column
         */
        @Override
        public String getColumnName(int columnIndex) {
            switch (columnIndex) {
                case 0:
                    return COLUMN_TYPE;
                case 1:
                    return COLUMN_SERVER;
                case 2:
                    return COLUMN_LOCAL;
                default:
                    return null;
            }
        }

        /**
         * Returns the most specific superclass for all the cell values
         * in the column.  This is used by the <code>JTable</code> to set up a
         * default renderer and editor for the column.
         *
         * @param columnIndex the index of the column
         * @return the common ancestor class of the object values in the model.
         */
        @Override
        public Class<?> getColumnClass(int columnIndex) {
            if (columnIndex == 0)
                return WorkingFolderType.class;
            return String.class;
        }

        /**
         * Returns the value for the cell at <code>columnIndex</code> and
         * <code>rowIndex</code>.
         *
         * @param rowIndex    the row whose value is to be queried
         * @param columnIndex the column whose value is to be queried
         * @return the value Object at the specified cell
         */
        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {
            WorkingFolderData item = data[rowIndex];
            switch (columnIndex) {
                case 0:
                    return item.getType();
                case 1:
                    return item.getServerItem();
                case 2:
                    return item.getLocalItem();
                default:
                    return null;
            }
        }

        /**
         * Returns true if the cell at <code>rowIndex</code> and
         * <code>columnIndex</code>
         * is editable.  Otherwise, <code>setValueAt</code> on the cell will not
         * change the value of that cell.
         *
         * @param rowIndex    the row whose value to be queried
         * @param columnIndex the column whose value to be queried
         * @return true if the cell is editable
         * @see #setValueAt
         */
        @Override
        public boolean isCellEditable(int rowIndex, int columnIndex) {
            return true;
        }

        /**
         * Sets the value in the cell at <code>columnIndex</code> and
         * <code>rowIndex</code> to <code>aValue</code>.
         *
         * @param aValue      the new value
         * @param rowIndex    the row whose value is to be changed
         * @param columnIndex the column whose value is to be changed
         * @see #getValueAt
         * @see #isCellEditable
         */
        @Override
        public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
            WorkingFolderData item = data[rowIndex];
            if (item == null) {
                item = new WorkingFolderData(null, null, null);
            }
            switch (columnIndex) {
                case 0:
                    item.setType((WorkingFolderType) aValue);
                    break;
                case 1:
                    item.setServerItem((String) aValue);
                    break;
                case 2:
                    item.setLocalItem((String) aValue);
                    break;
            }
            data[rowIndex] =  item;
            super.fireTableCellUpdated(rowIndex, columnIndex);
        }

    }


    private class WorkingFolderTypeCellRenderer extends DefaultTableRenderer {

        /**
         * Returns the component used for drawing the cell.  This method is
         * used to configure the renderer appropriately before drawing.
         * <p/>
         * The <code>TableCellRenderer</code> is also responsible for rendering the
         * the cell representing the table's current DnD drop location if
         * it has one. If this renderer cares about rendering
         * the DnD drop location, it should query the table directly to
         * see if the given row and column represent the drop location:
         * <pre>
         *     JTable.DropLocation dropLocation = table.getDropLocation();
         *     if (dropLocation != null
         *             && !dropLocation.isInsertRow()
         *             && !dropLocation.isInsertColumn()
         *             && dropLocation.getRow() == row
         *             && dropLocation.getColumn() == column) {
         *
         *         // this cell represents the current drop location
         *         // so render it specially, perhaps with a different color
         *     }
         * </pre>
         * <p/>
         * During a printing operation, this method will be called with
         * <code>isSelected</code> and <code>hasFocus</code> values of
         * <code>false</code> to prevent selection and focus from appearing
         * in the printed output. To do other customization based on whether
         * or not the table is being printed, check the return value from
         * {@link JComponent#isPaintingForPrint()}.
         *
         * @param table      the <code>JTable</code> that is asking the
         *                   renderer to draw; can be <code>null</code>
         * @param value      the value of the cell to be rendered.  It is
         *                   up to the specific renderer to interpret
         *                   and draw the value.  For example, if
         *                   <code>value</code>
         *                   is the string "true", it could be rendered as a
         *                   string or it could be rendered as a check
         *                   box that is checked.  <code>null</code> is a
         *                   valid value
         * @param isSelected true if the cell is to be rendered with the
         *                   selection highlighted; otherwise false
         * @param hasFocus   if true, render cell appropriately.  For
         *                   example, put a special border on the cell, if
         *                   the cell can be edited, render in the color used
         *                   to indicate editing
         * @param row        the row index of the cell being drawn.  When
         *                   drawing the header, the value of
         *                   <code>row</code> is -1
         * @param column     the column index of the cell being drawn
         * @see JComponent#isPaintingForPrint()
         */
        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
            WorkingFolderType type = (WorkingFolderType)value;
            if (WorkingFolderType.MAP.equals(type))
                value = TYPE_ACTIVE;
            else
                value = TYPE_CLOAKED;
            return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
        }
    }

    private class WorkingFolderPathCellRenderer extends DefaultTableRenderer{
        /**
         * Returns the component used for drawing the cell.  This method is
         * used to configure the renderer appropriately before drawing.
         * <p/>
         * The <code>TableCellRenderer</code> is also responsible for rendering the
         * the cell representing the table's current DnD drop location if
         * it has one. If this renderer cares about rendering
         * the DnD drop location, it should query the table directly to
         * see if the given row and column represent the drop location:
         * <pre>
         *     JTable.DropLocation dropLocation = table.getDropLocation();
         *     if (dropLocation != null
         *             && !dropLocation.isInsertRow()
         *             && !dropLocation.isInsertColumn()
         *             && dropLocation.getRow() == row
         *             && dropLocation.getColumn() == column) {
         *
         *         // this cell represents the current drop location
         *         // so render it specially, perhaps with a different color
         *     }
         * </pre>
         * <p/>
         * During a printing operation, this method will be called with
         * <code>isSelected</code> and <code>hasFocus</code> values of
         * <code>false</code> to prevent selection and focus from appearing
         * in the printed output. To do other customization based on whether
         * or not the table is being printed, check the return value from
         * {@link JComponent#isPaintingForPrint()}.
         *
         * @param table      the <code>JTable</code> that is asking the
         *                   renderer to draw; can be <code>null</code>
         * @param value      the value of the cell to be rendered.  It is
         *                   up to the specific renderer to interpret
         *                   and draw the value.  For example, if
         *                   <code>value</code>
         *                   is the string "true", it could be rendered as a
         *                   string or it could be rendered as a check
         *                   box that is checked.  <code>null</code> is a
         *                   valid value
         * @param isSelected true if the cell is to be rendered with the
         *                   selection highlighted; otherwise false
         * @param hasFocus   if true, render cell appropriately.  For
         *                   example, put a special border on the cell, if
         *                   the cell can be edited, render in the color used
         *                   to indicate editing
         * @param row        the row index of the cell being drawn.  When
         *                   drawing the header, the value of
         *                   <code>row</code> is -1
         * @param column     the column index of the cell being drawn
         * @see JComponent#isPaintingForPrint()
         */
        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
            return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
        }
    }

    private static class WorkingFolderTypeCellEditor extends DefaultCellEditor{
        //This combo is only used to in the Table for Editing the Folder type
        private final JComboBox<WorkingFolderType> workingFolderTypeJComboBox;


        public WorkingFolderTypeCellEditor(){
            super(new JComboBox<WorkingFolderType>());

            workingFolderTypeJComboBox = (JComboBox<WorkingFolderType>) super.getComponent();
            workingFolderTypeJComboBox.setRenderer(new DefaultListCellRenderer() {
                @Override
                public Component getListCellRendererComponent(
                        JList<?> list,
                        Object value,
                        int index,
                        boolean isSelected,
                        boolean cellHasFocus) {
                    WorkingFolderType type = (WorkingFolderType) value;
                    if (WorkingFolderType.MAP.equals(type))
                        value = TYPE_ACTIVE;
                    else
                        value = TYPE_CLOAKED;

                    return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
                }
            });
            workingFolderTypeJComboBox.setModel(new WorkingFolderTypeComboModel());
        }
    }



    private static class WorkingFolderTypeComboModel extends SortedComboBoxModel<WorkingFolderType>{
        private static Comparator<WorkingFolderType> typeComparator = new Comparator<WorkingFolderType>() {
            @Override
            public int compare(WorkingFolderType o1, WorkingFolderType o2) {
                String s1 = WorkingFolderType.MAP.equals(o1) ? TYPE_ACTIVE : TYPE_CLOAKED;
                String s2 = WorkingFolderType.MAP.equals(o1) ? TYPE_ACTIVE : TYPE_CLOAKED;
                return String.CASE_INSENSITIVE_ORDER.compare(s1, s2);
            }
        };

        public WorkingFolderTypeComboModel() {
            super(typeComparator);
            super.addAll(new WorkingFolderType[] { WorkingFolderType.MAP, WorkingFolderType.CLOAK });
        }
    }

    private class PathTableCellEditor extends AbstractCellEditor implements TableCellEditor {
        private final TextFieldWithBrowseButton editorComponent;
        protected EventListenerList listenerList = new EventListenerList();

        private final FileChooserDescriptor localDescriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor();
        private final ComponentWithBrowseButton.BrowseFolderActionListener localListener;
//        ComponentWithBrowseButton.BrowseFolderActionListener localListener = new ComponentWithBrowseButton
//                .BrowseFolderActionListener(Messages.getString("SelectServerAndLocalPathWizardStep.control.BrowseActionTitle"),
//                Messages.getString("SelectServerAndLocalPathWizardStep.control.BrowseActionDesc"),
//                editorComponent, null, descriptor, TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT);

        private final ActionListener remoteBrowser = new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
            ServerItemTreeDialog dialog = new ServerItemTreeDialog(WorkingFolderDataTable2.this, null, workspace, "", "");
            if(dialog.showAndGet())
                editorComponent.setText(dialog.getSelectedPath());
            }
        };

        public PathTableCellEditor(){
            editorComponent = new TextFieldWithBrowseButton();
            localListener = new ComponentWithBrowseButton
                    .BrowseFolderActionListener(Messages.getString("SelectServerAndLocalPathWizardStep.control.BrowseActionTitle"),
                    Messages.getString("SelectServerAndLocalPathWizardStep.control.BrowseActionDesc"),
                    editorComponent, null, localDescriptor, TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT);
        }

        /**
         * Sets an initial <code>value</code> for the editor.  This will cause
         * the editor to <code>stopEditing</code> and lose any partially
         * edited value if the editor is editing when this method is called. <p>
         * <p/>
         * Returns the component that should be added to the client's
         * <code>Component</code> hierarchy.  Once installed in the client's
         * hierarchy this component will then be able to draw and receive
         * user input.
         *
         * @param table      the <code>JTable</code> that is asking the
         *                   editor to edit; can be <code>null</code>
         * @param value      the value of the cell to be edited; it is
         *                   up to the specific editor to interpret
         *                   and draw the value.  For example, if value is
         *                   the string "true", it could be rendered as a
         *                   string or it could be rendered as a check
         *                   box that is checked.  <code>null</code>
         *                   is a valid value
         * @param isSelected true if the cell is to be rendered with
         *                   highlighting
         * @param row        the row of the cell being edited
         * @param column     the column of the cell being edited
         * @return the component for editing
         */
        @Override
        public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
            removeActionListeners();
            if (table.getColumnName(column).equals(WorkingFolderDataModel.COLUMN_LOCAL)){
                editorComponent.addActionListener(localListener);
            } else {
                editorComponent.addActionListener(null);
            }
            return editorComponent;
        }

        /**
         * Returns the value contained in the editor.
         *
         * @return the value contained in the editor
         */
        @Override
        public Object getCellEditorValue() {
            return editorComponent.getText();
        }



        private void removeActionListeners(){
            editorComponent.removeActionListener(localListener);
            editorComponent.removeActionListener(remoteBrowser);
        }

    }
}
